/* Rev. 06 - added R/O-R/W & DIR/SYS file attribute handling, sector (de)interleaving procedure */


#define _GNU_SOURCE	/* required by asprintf() */
#include <stdio.h>
#include <sys/stat.h>	/* required by stat type */
#include <stdlib.h>	/* required by exit() function */
#include <errno.h>	/* required by system var errno (?) */
#include <string.h>	/* required by string functions starting named "str*" */
#include <ctype.h>	/* required by isdigit() */
#include <fcntl.h>	/* required by low-level file access */
#include <unistd.h>	/* required by low-level file deletion function "unlink" */
#include <libgen.h>	/* required by dirname and basename */
#include <sys/types.h>	/* required by opendir() */
#include <dirent.h>	/* required by opendir() */


char *USAGE = 
"\n\n===============================================================================\n\
| *********** CoBra DEVIL raw disk image reader / writer for Linux ********** |\n\
|                CREATED BY: CoBra @ RomanianHomeComputer                     |\n\
===============================================================================\n\
\n\
                 PLEASE READ THIS CAREFULLY BEFORE USING\n\
\n\
######################### PURPOSE OF THIS PROGRAM #############################\n\
\n\
This program reads and writes RAW FORMAT floppy images of DSDD 720K CoBra Devil\n\
disks with 80 tracks, 18 sectors/track/side (36 sectors/track), 256 bytes/sector\n\
and displays a detailed directory structure and catalog listing. It can extract\n\
selected catalog entries from the floppy image to an external .TAP file\n\
according to the Spectrum .TAP file format specifications by Gerton Lunter.\n\
 It can also write a .TAP file (existent in your Linux filesystem) into the\n\
filesystem of the disk image. A full list of commands is given below.\n\
\n\
 The disk image format must have the 256-byte sectors in their physical order,\n\
starting with side 0 sectors of a track and continuing with side 1 sectors of it.\n\
Please note that the logical order of sectors differs from the physical order.\n\
If the physical sector order is 1 2 3 4 5 ... 18, their logical order (in\n\
which they are read/written) is 1 10 2 11 3 12 ... 9 18 (interleave 2).\n\
All tracks must be saved like this in physical order (track#0, track#1 ...).\n\
\n\
 A Devil RAW floppy image file can be generated from an already existing Teledisk\n\
floppy image using the TD02RAW utility that comes with this program\n\
(it uses the SAMDisk utility by Simon Owen run under Linux Wine, (Wine must be\n\
configured as Windows 2000 or up).\n\
 A Devil RAW floppy image file can also be created with this program (using\n\
the -c command), and then applications available as .TAP files can be written to it.\n\
 Since virtualization software is usually too stupid to work with 256 bytes/sector\n\
floppy disks, generating a Devil RAW floppy image in a virtual DOS machine will\n\
most likely never work.\n\
\n\
########################### SYNTAX DESCRIPTION ################################\n\
\n\
 The general command line syntax for this program is:\n\
---------------------------------------------------\n\
executable command /path/to/devildisk.img   ...\n\
---------------------------------------------------\n\
          | arg#1 |        arg#2         |  ...\n\
\n\
 Commands supported are:\n\
	-c (create disk image),\n\
	-l (list contents),\n\
	-x (extract file(s)),\n\
	-d (delete file(s)),\n\
	-w (write file(s) to disk image),\n\
	-r0 (set R/O-R/W attribute to R/W\n\
	-r1 (set R/O-R/W attribute to R/O\n\
	-s0 (set DIR/SYS attribute to DIR, visible with CAT),\n\
	-s1 (set DIR/SYS attribute to SYS, non-visible with CAT),\n\
	-i (interleave disk image, this program only works with interleaved images),\n\
	-di (deinterleave disk image before converting to HFE,\n\
	    HxCFloppyEmulator.exe expects RAW images to be non-interleaved,\n\
	    the interleave factor will be have to be entered in the\n\
	    \"RAW File format configuration\" window before exporting to HFE)\n\
	-k (check disk image for some logical format errors - not implemented yet!!).\n\
\n\
The following options can be used with SOME commands (see each command syntax):\n\
	-log - a detailed operation log file will be generated.\n\
	-ver - verbose operation, detailed messages will be displayed during execution.\n\
	-deb - debug information will be displayed, beside the normally displayed info:\n\
	     - a raw hex text dump of all directory records, in logical sector order\n\
	       containing the file names in clear for records not deleted,\n\
	     - a directory record allocation table in logical sector order, where\n\
	       a \"0\" means the record is free (available) and\n\
	       a \"1\" means it is allocated,\n\
\n\
\n\
 The create command line syntax is:\n\
------------------------------------------\n\
executable   -c    /path/to/devildisk.img \n\
------------------------------------------\n\
          | arg#1 |        arg#2         |\n\
\n\
\n\
 The list command line syntax is:\n\
--------------------------------------------------\n\
executable   -l    /path/to/devildisk.img  [-deb] \n\
--------------------------------------------------\n\
          | arg#1 |        arg#2         | arg#3 |\n\
\n\
\n\
 The extract command line syntax is:\n\
-----------------------------------------------------------------------\n\
executable   -x    /path/to/devildisk.img  range  [ -log|-ver [-deb] ] \n\
-----------------------------------------------------------------------\n\
          | arg#1 |        arg#2         | arg#3 |   arg#4   | arg#5  |\n\
\n\
\n\
 The write file command line syntax is:\n\
----------------------------------------------------------------------------\n\
executable   -w    /path/to/devildisk.img /path/to/file.tap [ -ver  [-deb] ]\n\
----------------------------------------------------------------------------\n\
          | arg#1 |         arg#2        |      arg#3      | arg#4 | arg#5 |\n\
\n\
\n\
 The delete command line syntax is:\n\
----------------------------------------------------------------\n\
executable   -d    /path/to/devildisk.img  range  [-ver [-deb]]]\n\
----------------------------------------------------------------\n\
          | arg#1 |         arg#2        | arg#3 |arg#4| arg#5 |\n\
\n\
\n\
 The interleaving command line syntax is:\n\
----------------------------------------------------------\n\
executable   -i    /path/to/devildisk.img [ -ver  [-deb] ]\n\
----------------------------------------------------------\n\
          | arg#1 |         arg#2        | arg#4 | arg#5 |\n\
\n\
\n\
 The deinterleaving command line syntax is:\n\
----------------------------------------------------------\n\
executable   -di   /path/to/devildisk.img [ -ver  [-deb] ]\n\
----------------------------------------------------------\n\
          | arg#1 |         arg#2        | arg#4 | arg#5 |\n\
\n\
\n\
 The set attribute command line syntax is:\n\
------------------------------------------------------------------------\n\
executable -r0|-r1|-s0|-s1 /path/to/devildisk.img  range  [-ver [-deb]]]\n\
------------------------------------------------------------------------\n\
          |     arg#1     |         arg#2        | arg#3 |arg#4| arg#5 |\n\
\n\
\n\
NOTE: 1. FOR -x, -d and -r0|-r1|-s0|-s1 COMMANDS, arg#3 is a range of\n\
	 catalog entries in the form \"nn\" or \"nn-nn\", where nn is\n\
	 a 1-digit or 2-digit number between 0 and 76\n\
	 (there can be max. 77 catalog entries).\n\
      2. FOR THE -w COMMAND, arg#3 is the full path to a .TAP file in your\n\
         (Linux) computer filesystem, to be written to the disk image.\n\
      3. FOR THE -l COMMAND, arg#3 is optional and can only be \"-deb\".\n\
      4. FOR THE -c COMMAND, arg#3 is never used.\n\
      5. Command parameters must be in the exact position as specified\n\
         by the syntax. For instance, with the -x command, the -deb option\n\
         must always be arg#5. That means -log or -ver must be used first.\n\
\n\
 The disk image file name MUST end in an .img extension.\n\
\n\
 The -c command creates an empty formatted disk image (all sectors filled\n\
with the E5 byte).\n\
\n\
 The -l command displays the detailed contents of the disk image file.\n\
\n\
 The -x command will extract a user specified range of catalog entries\n\
(the range can be one single entry or multiple consecutive entries) to an\n\
external .TAP file which can then be loaded in any Spectrum emulator or in\n\
a physical Spectrum compatible (e.g. CoBra) by playing it as audio.\n\
 When specifying the range we should have in mind the data sequence of a\n\
standard Spectrum application, which usually is \"Prog: Code: Code: ...\"\n\
Sometimes it can also be \"Prog: Prog: Code: Code:\" but whatever that is\n\
it should be precisely known before attempting to extract an application\n\
from the disk image, so we can correctly specify the range of catalog entries.\n\
 If the range is not correctly specified, the extracted .TAP file may end up\n\
containing an incomplete Spectrum application which will not load correctly\n\
later on.\n\
\n\
 The -w command will write the contents of a .TAP file existent on your (Linux)\n\
filesystem into the disk image specified. The writing will only take place if\n\
a continuous range of disk blocks, (i.e. full tracks) big enough to contain the\n\
whole contents of the .TAP file, can be found on the disk image. The writing\n\
will put all data blocks from the .TAP file, in the same consecutive order,\n\
on the consecutive free tracks found on the disk image.\n\
\n\
 The -d command will delete a user specified range of catalog entries\n\
(the range can be one single entry or multiple consecutive entries) from\n\
the disk image file. The inevitable effect of this will be disk fragmentation\n\
which later on will affect the writing of another application from a .TAP file.\n\
The proper way to do the deletion should involve an automatic defragmentation,\n\
but so far I did have the time to implement this :( sorry ).\n\
\n\
 The -i command will take a RAW DEVIL disk image (NNNN.img) with sectors presumed\n\
to be non-interleaved and generate a new image (NNNN_interleaved.img) having\n\
sectors saved in their normal interleaved order according to the DEVIL disk format.\n\
After converting from .TD0 to RAW using HxCFloppyEmulator.exe, the RAW image\n\
must be first interleaved before manipulating it with this program, otherwise\n\
data errors will occur.\n\
\n\
 The -di command will take a RAW DEVIL disk image (NNNN.img) with sectors presumed\n\
to be interleaved and generate a new image (NNNN_deinterleaved.img) having\n\
sectors saved in the order of their physical numbers (non-interleaved.\n\
Before converting from RAW to HFE using HxCFloppyEmulator.exe, the RAW image\n\
must be first deinterleaved, otherwise the HFE image will not be usable.\n\
\n\
 The -r0 command will set the writable attribute to R/W for the data blocks\n\
in the specified range (the range has the same meaning as for the -d command).\n\
Blocks with this attribute can be deleted using the ERASE command or the STAT utility.\n\
\n\
 The -r1 command will set the writable attribute to R/O for the data blocks\n\
in the specified range (the range has the same meaning as for the -d command).\n\
Blocks with this attribute can NOT be deleted.\n\
\n\
 The -s0 command will set the visibility attribute to DIR for the data blocks\n\
in the specified range (the range has the same meaning as for the -d command).\n\
Blocks with this attribute can be listed using the DEVIL command CAT.\n\
\n\
 The -s1 command will set the visibility attribute to SYS for the data blocks\n\
in the specified range (the range has the same meaning as for the -d command).\n\
Blocks with this attribute cannot be listed using the DEVIL command CAT.\n\
################################## END ########################################\n\
";


int main (int argc, char *argv[]) {

	struct stat st;		

	unsigned char buffer[] = "\xE5\xE5\xE5\xE5\xE5\xE5\xE5\xE5\xE5\xE5";	/* used as write data when generating formatted CP/M disk image */
	char *diskimg_file_name;
	int diskimg_file_descrptr;	/* disk image file descriptor */
	FILE *test_stream;		/* disk image file stream - used only for initial check in main() */
	char tap_file_name[100];
	char tap_log_file_name[100];
	int tap_file_descrptr;		/* .TAP file descriptor */
	FILE *tap_log_file_stream;	/* .TAP log file stream */

	int error_code;
	unsigned int i, j, k;

	/* ############################### variables used by readblock() ##################################### */
	unsigned short track_length = 9216;	/* length of a track in bytes (2 sides x 18 sectors x 256 bytes) */
	unsigned short sstracklength = 4608;	/* length of a track side in bytes (18 sectors x 256 bytes) */
	int  xlt[18] = {0, 2, 4, 6, 8,10,12,14,16, 1, 3, 5, 7, 9,11,13,15,17};	/* used for computing the start of sectors */
/*	int xlt1[18] = {1,10, 2,11, 3,12, 4,13, 5,14, 6,15, 7,16, 8,17, 9,18};	/* used for computing the physical sector number */
	const int xlt2[36] = {0, 2, 4, 6, 8,10,12,14,16, 1, 3, 5, 7, 9,11,13,15,17,18,20,22,24,26,28,30,32,34,19,21,23,25,27,29,31,33,35};	/* used for computing the start of sectors in an entire track */

	/* ############################### variables used by devildirdump() #################################### */
	unsigned char devildir0_mem[1386];	/* 77 dir sectors x 1 entry/sector x 18 bytes/entry = 1386 bytes */	/* stores the entire directory */
	unsigned char devildir_mem[1463];	/* 77 dir sectors x 1 entry/sector x 19 bytes/entry = 1463 bytes (one extra byte to store index of dir entry) */	/* stores only used dir entries (not deleted) */
	unsigned char devildir1_mem[1463];	/* 77 dir sectors x 1 entry/sector x 19 bytes/entry = 1463 bytes (one extra byte to store index of dir entry) */	/* stores only used & relevant dir entries (not deleted and !=E4) */
	int nr_recs;				/* nr of allocated dir entries (records) */
	int nr_files;				/* nr of actual files on disk (nr_files < nr_recs <= 77) */
	char alloc_dir[77];			/* map of free and allocated dir entries (records) */
	unsigned char free_dir_recs;		/* nr. of free directory entries (records) */

	/* ############################### variables used by devilfilewrite() & writeblock() ###################### */
	unsigned char tap_file_buffer[0x10000];
	unsigned char track_buffer[9216];
	/* #################################################################################################### */

	unsigned char checksum, checksum_h;	/*, checksum_d;		/* checksums for reading, header and data */
	signed char blocks_needed;

	char dest_dir_base[200];	/* disk image saving dir */

	unsigned char range1, range2;			/* range limits for files to extract */
	char range1_c[5], range2_c[5];
	for (i=0; i<5; i++) {range1_c[i]='\0'; range2_c[i]='\0';}
	unsigned char dashpos;				/* position of dash in string */

	unsigned char create_mode=0;
	unsigned char extract_mode=0;
	unsigned char list_mode=0;
	unsigned char write_mode=0;
	unsigned char delete_mode=0;
	unsigned char check_mode=0;
	unsigned char interleave_mode=0;
	unsigned char deinterleave_mode=0;
	unsigned char set_attr_mode=0;
	unsigned char log_on=0;		/* 0 if no log to generate, 1 if log */
	unsigned char verbose=0;	/* 0 if no detailed messages to generate, 1 if yes */
	unsigned char debug=0;		/* debug mode flag (print extra info): 0 = normal mode, 1 = debug mode */

	/* Find the index of a dot char in a string */
	int dotpos (char *string) {
		char *e;
		int index;

		if (strchr(string, '.') == NULL) {return(0);}
		e = strchr(string, '.'); index = (int)(e - string);
		return(index);
	}

	/* Count dots in a string */
	int dotcount (char *string) {
		unsigned short i, j=0;

		for (i=0; i<strlen(string); i++) {
			if (string[i] == '.') {j++;}
		}
		return(j);
	}

	/* Test if dir exists (1 success, -1 does not exist, -2 not dir) */
	int xis_dir (char *d) {
		DIR *dirptr;

		if (access ( d, F_OK ) != -1) {			/* file exists */
			if ((dirptr = opendir (d)) != NULL) {
				closedir (dirptr); return 1;	/* d exists, and is dir */
			} else {return -2;}			/* d exists, but not dir */
		} else {return -1;}				/* d does not exist */
	}
	
	/* Generate common directory for files extracted from disk image - in global string dest_dir_base */
	void diskimg_dir () {
		unsigned char i, ix;

		for (i=0; i<200; i++) {dest_dir_base[i]='\0';}
		sprintf(dest_dir_base, "%s", basename(diskimg_file_name));	/* dest_dir_base = extraction dir for disk image */
		ix = dotpos(dest_dir_base); if (ix > 0) {for (i=ix; i<200; i++) {dest_dir_base[i]='\0';} }
	}

	void range_check () {
		dashpos=0;
		for (i=0; i<strlen(argv[3]); i++) {
			if (!isdigit(argv[3][i]) && (argv[3][i] != '-')) {printf ("Wrong syntax for file range.\n"); exit(0);}
			if ((argv[3][i] == '-') && ((i == 0) || (i == strlen(argv[3])-1))) {printf ("Wrong syntax for file range, dash should be in between 2 numbers.\n"); exit(0);}
			if  (argv[3][i] == '-') {
				if (dashpos == 0) {dashpos = i;} else {printf ("No multiple dashes allowed in file range.\n"); exit(0);}
			}
		}
		if ((dashpos > 2) || ((dashpos > 0) && (dashpos < strlen(argv[3])-3))) {printf("Catalog order numbers can only have 2 digits.\n"); exit(0);}
		if (dashpos != 0) {	/* multiple catalog entries to be extracted */
			memcpy(&range1_c[0], &argv[3][0], dashpos); memcpy(&range2_c[0], &argv[3][0]+dashpos+1, strlen(argv[3])-dashpos-1);
			sscanf(range1_c, "%hhd", &range1); sscanf(range2_c, "%hhd", &range2);
			if (range1 >= range2) {printf("First number should be < the second.\n"); exit(0);}
		} else {		/* one single catalog entry to be extracted */
			if (strlen(argv[3])>2) {printf("File order number can only have 2 digits.\n"); exit(0);}
			for (i=0; i<strlen(argv[3]); i++) { if (!isdigit(argv[3][i])) {printf ("Wrong syntax for file range.\n"); exit(0);} }
			memcpy(&range1_c[0], &argv[3][0], strlen(argv[3]));
			sscanf(range1_c, "%hhd", &range1); strcpy(range2_c, "-"); range2 = range1;
		}
/*		printf("range_check(): catalog entries to process: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2); */
	}

/*	######################################### STRUCTURE OF A DEVIL CATALOG ENTRY ##############################################

	CATALOG TAKES UP FIRST 3 TRACKS BOTH SIDES (TRACKS 0,1,2), HAS MAX. 77 ENTRIES. LOGICAL ALLOCATION UNIT IS 1 TRACK BOTH SIDES (9 KB).

	  00   01   02   03   04   05   06   07   08   09   10   11   12   13   14   15   16   17
	+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
	| TY | N0 | N1 | N2 | N3 | N4 | N5 | N6 | N7 | N8 | N9 | D0 | D1 | A0 | A1 | B0 | B1 | SY |
	+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

	Field meanings:		TY = DATA TYPE (00 = program, 01 = number array, 02 = character array, 03 = code, E4 = >=2nd CATALOG ENTRY FOR SAME FILE, E5 = DELETED FILE)
				
				N0...N9 = DATA BLOCK NAME (10 characters, padded with trailing blanks)

				D0 = DATA BLOCK LENGTH (Low byte)	A0 = PARAMETER 1 (Low byte)	B0 = PARAMETER 2 (Low byte)
				D1 = DATA BLOCK LENGTH (High byte)	A1 = PARAMETER 1 (High byte)	B1 = PARAMETER 2 (High byte)

				SY = SYSTEM/HIDDEN ATTRIBUTE: SY = 0 FOR NORMAL, SY = 1 FOR SYSTEM/HIDDEN

	########################################################################################################################### */

	void devildirdump() {

		char *record, type[5], leng[6], par1[6], par2[6], varN[2], sys[16];
		unsigned char i;
		int diskimg_file_descrptr;			/* disk image file descriptor */
		unsigned char rb[18];				/* 18 byte read buffer */
		unsigned int dir_sect_start;
		unsigned char dir_entry_idx;	/* counts all dir entries (0 to 127) */

		/* ########################### OPEN DISK IMAGE FILE FOR READING ############################## */
		diskimg_file_descrptr = open (diskimg_file_name, O_RDONLY); if (diskimg_file_descrptr == -1) {printf("devildirdump() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}
		/* ########################################################################################### */

		for (i=0; i<77; i++) {alloc_dir[i]=0;}		/* clear dir allocation table */

		nr_recs=0; nr_files=0;
		dir_entry_idx=0;
		free_dir_recs=77;
		if (debug) {printf ("\n                      DIRECTORY ENTRIES:\n==============================================================\n------------------- (logical sector order) -------------------\nRec#    Typ|             Name            | Len | Pa1 | Pa2 |\n--------------------------------------------------------------\n");}

		for (i=0; i<77; i++) {
			dir_sect_start = (i/18)*sstracklength + xlt[i%18]*256;
			lseek (diskimg_file_descrptr, dir_sect_start, SEEK_SET);
			read (diskimg_file_descrptr, (void *) rb, 18);

			asprintf (&record, "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", \
				rb[0],rb[1],rb[2],rb[3],rb[4],rb[5],rb[6],rb[7],rb[8],rb[9],rb[10],rb[11],rb[12],rb[13],rb[14],rb[15],rb[16],rb[17]);	/* text hexdump of a 18-byte dir entry */
			if (strcmp(record, "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00") == 0)
				{ printf ("Invalid disk image - dir entries cannot be full of zeros\n"); exit(0); }
			/* --------------- save each dir entry (even if empty) in the "devildir0_mem" variable ---------------- */
			memcpy((&devildir0_mem[0] + dir_entry_idx*18), &rb[0], 18); dir_entry_idx++;
			/* --------------- save each dir entry (if not deleted) in the "devildir_mem" variable ---------------- */
			/* --------------- add 1 extra byte in front as index of catalogue records holding the ---------------- */
			/* --------------- order number of record in catalogue (0 ... 76) ------------------------------------- */
			if ((rb[0]<=3) || (rb[0]==0xE4)) {
				memcpy((&devildir_mem[0] + nr_recs*19 + 1), &rb[0], 18); devildir_mem[0+nr_recs*19]=i; nr_recs++;
				alloc_dir[i]=1;		/* update directory record allocation map */
				free_dir_recs--;	/* update the nr. of allocated records */
				if (debug) {printf (" %02d ---> %02X %2c %2c %2c %2c %2c %2c %2c %2c %2c %2c %02X %02X %02X %02X %02X %02X %02X\n", i, rb[0],rb[1],rb[2],rb[3],rb[4],rb[5],rb[6],rb[7],rb[8],rb[9],rb[10],rb[11],rb[12],rb[13],rb[14],rb[15],rb[16],rb[17]);}
			} else {if (debug) {printf (" %02d ---> %s\n", i, record);}}
			/* --------------- save each relevant dir entry (if not deleted) in the "devildir1_mem" variable ------ */
			if (rb[0]<=3) {
				memcpy((&devildir1_mem[0] + nr_files*19 + 1), &rb[0], 18); devildir1_mem[0+nr_files*19]=i; nr_files++;
			}
			free(record);
		}
		if (debug) {
			printf("\n==============================================================\n\n\n");
			printf("--------------------------------------\n- DIRECTORY RECORD ALLOCATION TABLE --\n--------------------------------------\n");
			for (i=0; i<7; i++) {printf("trk. %02d - %d  %d  %d  %d  %d  %d  %d  %d  %d  %d\n", i*10, alloc_dir[i*10], alloc_dir[i*10+1], alloc_dir[i*10+2], alloc_dir[i*10+3], alloc_dir[i*10+4], alloc_dir[i*10+5], alloc_dir[i*10+6], alloc_dir[i*10+7], alloc_dir[i*10+8], alloc_dir[i*10+9]);}
			printf("trk. %02d - %d  %d  %d  %d  %d  %d  %d\n", 7*10, alloc_dir[7*10], alloc_dir[7*10+1], alloc_dir[7*10+2], alloc_dir[7*10+3], alloc_dir[7*10+4], alloc_dir[7*10+5], alloc_dir[7*10+6]);
			printf("--------------------------------------\n");
		}
		printf("\n");

		/* ################################## CLOSE DISK IMAGE FILE ################################## */
		close(diskimg_file_descrptr);
		/* ########################################################################################### */

		if (nr_recs == 0) {
			printf ("\n----------------------------------------------------------\n                   DISK IMAGE CONTENTS:\n----------------------------------------------------------\n\nThis disk is empty (just formatted or all files deleted)\n\n----------------------------------------------------------\n\n");
			if (extract_mode) {exit(0);}
		} else {
			printf("\n                       DISK IMAGE CONTENTS:\n");
			printf("======================================================================\n");
			printf(" ##    Type    Name       Length  Start VarStart VarN R/W-R/O DIR/SYS\n");
			printf("======================================================================\n");
/*			j=1;	*/
			for (i=0; i<nr_files; i++) {
/*			    if (devildir_mem[i*19+1]!=0xE4) {	*/
				switch (devildir1_mem[i*19+1]) {
					case 0 : strcpy(type,"Prog"); if (devildir1_mem[i*19+1+14]<0x80) {sprintf(par1, "%5d", devildir1_mem[i*19+1+13]+devildir1_mem[i*19+1+14]*256);} else {strcpy(par1, "     ");}  sprintf(par2, "%5d", devildir1_mem[i*19+1+15]+devildir1_mem[i*19+1+16]*256); strcpy(varN, " ");
					break;
					case 1 : strcpy(type,"NArr"); strcpy(par1, "     "); strcpy(par2, "     "); sprintf(varN, "%c", devildir1_mem[i*19+1+13]);
					break;
					case 2 : strcpy(type,"CArr"); strcpy(par1, "     "); strcpy(par2, "     "); sprintf(varN, "%c", devildir1_mem[i*19+1+13]);
					break;
					case 3 : strcpy(type,"Code"); sprintf(par1, "%5d", devildir1_mem[i*19+1+13]+devildir1_mem[i*19+1+14]*256); strcpy(par2, "     "); strcpy(varN, " ");
				}
				switch (devildir1_mem[i*19+18]) {
					case 0 : strcpy(sys,"R/W     DIR    "); break;
					case 1 : strcpy(sys,"R/W         SYS"); break;
					case 2 : strcpy(sys,"    R/O DIR    "); break;
					case 3 : strcpy(sys,"    R/O     SYS"); break;
					default : strcpy(sys,"???");
				}
				sprintf(leng, "%5d", devildir1_mem[i*19+1+11]+devildir1_mem[i*19+1+12]*256);
				printf(" %2d    %s    %c%c%c%c%c%c%c%c%c%c  %s  %s  %s  %s     %s\n", \
					i, type, devildir1_mem[i*19+1+1], devildir1_mem[i*19+1+2], devildir1_mem[i*19+1+3], devildir1_mem[i*19+1+4], \
						 devildir1_mem[i*19+1+5], devildir1_mem[i*19+1+6], devildir1_mem[i*19+1+7], devildir1_mem[i*19+1+8], \
						 devildir1_mem[i*19+1+9], devildir1_mem[i*19+1+10], \
					leng, par1, par2, varN, sys);
/*				j++;	*/
/*			    }	*/
			}
			printf("======================================================================\n");
/*			printf("*** NOTE: ***\n"); */
			printf("|  For Prog: \"Start\" = Autostart Line Number                         |\n");
			printf("|            \"VarStart\" = Var Area offset from start                 |\n");
			printf("|  For Code: \"Start\" = Code Block Start (Load) Address               |\n");
			printf("|  For XArr: \"VarN\"  = Name of the character/numeric array variable  |\n");
			printf("======================================================================\n\n\n");
		}

	}

	/* Reads one block (a full track, both sides of it) from the disk image and saves it in a .TAP file.
	 * This function will open both files, the .TAP file will be already created because the first data header
	 * will have been saved to it by devilfilecopy() before the actual block data saved by this function. */
	void readblock(unsigned char block_no, unsigned short data_size) {	/* data_size must be <= 9216, make sure when calling this function */
		unsigned short count;
		unsigned char read_buffer[256];
		int i, j;
		if (debug) {printf("readblock(): reading %d bytes from block/track %d:\n", data_size, block_no);}
/*		if (log_on && (tap_log_file_stream != NULL)) {fprintf(tap_log_file_stream, "reading %d bytes from block/track %d:\n", data_size, block_no);} */
		diskimg_file_descrptr = open (diskimg_file_name, O_RDONLY); if (diskimg_file_descrptr == -1) {printf("readblock() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}
/*		umask(000);	*/
		tap_file_descrptr = open (tap_file_name, O_WRONLY | O_APPEND, 00666); if (tap_file_descrptr == -1) {printf("readblock() error: tap_file_descrptr ERROR %d: %m\n", errno); exit(0);}
		for (i=0; i<data_size/256; i++) {
			count = 256;
			lseek (diskimg_file_descrptr, track_length*(3+block_no)+xlt2[i]*256, SEEK_SET);
			read (diskimg_file_descrptr, (void *) read_buffer, count);
			for (j=0; j<count; j++) {checksum ^= read_buffer[j];}		/* update checksum */
			error_code = write (tap_file_descrptr, (void *) read_buffer, count); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
		}
/*printf("i=%d, data_size/256=%d", i, data_size/256);*/
		if (data_size % 256) {
/*			i++;	*/
			count = data_size % 256;
			lseek (diskimg_file_descrptr, track_length*(3+block_no)+xlt2[i]*256, SEEK_SET);
			read (diskimg_file_descrptr, (void *) read_buffer, count);
			for (j=0; j<count; j++) {checksum ^= read_buffer[j];}		/* update checksum */
			error_code = write (tap_file_descrptr, (void *) read_buffer, count); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
		}

		close(diskimg_file_descrptr); close(tap_file_descrptr);
	}

	/* Writes one block (a full track, both sides of it) to the disk image.
	 * Data to be written must already exist in "track_buffer".
	 * devilfilewrite() will copy data from tap_file_buffer[] to track_buffer[] before calling this function.
	 * This function will open diskimg_file_name and close it when done. */
	void writeblock(unsigned char block_no, unsigned short data_size) {	/* data_size must be <= 9216, make sure when calling this function */
		unsigned short count;
/*		unsigned int diskimg_file_address; */
		unsigned char write_buffer[256];
		int i;
		if (debug) {printf("writeblock(): writing %d bytes to track %d\n", data_size, block_no+3);}
		diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("readblock() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}
/*		tap_file_descrptr = open (tap_file_name, O_RDONLY, 00666); if (tap_file_descrptr == -1) {printf("readblock() error: tap_file_descrptr ERROR %d: %m\n", errno); exit(0);} */
		for (i=0; i<data_size/256; i++) {
			count = 256;
			memcpy(&write_buffer[0], &track_buffer[i*256], count);	/* the first 3 bytes in tap_file_buffer are protocol bytes, not part of actual data */
			/* -------------------------------------------------------------------------------------------------- */
			error_code = pwrite (diskimg_file_descrptr, (void *) write_buffer, count, 9216*(3+block_no)+xlt2[i]*256); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
			fsync (diskimg_file_descrptr);
			/* -------------------------------------------------------------------------------------------------- */
		}
/*printf("i=%d, data_size/256=%d", i, data_size/256);*/
		if (data_size % 256) {
			count = data_size % 256;
			memcpy(&write_buffer[0], &track_buffer[i*256], count);	/* the first 3 bytes in tap_file_buffer are protocol bytes, not part of actual data */
			/* -------------------------------------------------------------------------------------------------- */
			error_code = pwrite (diskimg_file_descrptr, (void *) write_buffer, count, 9216*(3+block_no)+xlt2[i]*256); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
			fsync (diskimg_file_descrptr);
			/* -------------------------------------------------------------------------------------------------- */
		}
		close(diskimg_file_descrptr);
/*		close(tap_file_descrptr); */
	}

	/* Saves a single catalog entry or a range of entries to a .TAP file according to the .TAP file format.
	 * The name of the .TAP file will be the name of the first catalog entry in the given range.
	 * The .TAP file will be created in this function, when the first header is generated and saved to it.
	 * Then the .TAP file is closed and will later be opened and closed (along with the disk image file)
	 * by the readblock() function which is called in here. */
	void devilfilecopy(unsigned char range1, unsigned char range2) {

		unsigned char header[24];		/* save buffer holding header data + first 3 bytes of data block (data block length + flag (FF)) */
		header[0]=0x13; header[1]=0x00;		/* header size is always 19 bytes (0x0013) */
		header[2]=0x00;				/* flag byte = 00 for headers */
		unsigned short leftover_size, data_length;
		char name[11], name1[11];		/* standard 10 char Spectrum program name */
		unsigned char params[6], params1[6];
		unsigned char dir_index;

		/* ############################# CHECK FOR EXISTING DISK IMAGE DIR ###################################### */
		diskimg_dir ();		/* set disk image base saving dir - in dest_dir_base */

		umask(000);
		switch (xis_dir(dest_dir_base)) {	/* check if disk image base directory exists */
							/* if not, create it: */
			case  1 : printf("Using already existing base directory %s\n", dest_dir_base);
			
			break;
			
			case -1 : if (mkdir (dest_dir_base, 0777) != 0) {printf ("Error %d creating disk image directory %s: %m\n", errno, dest_dir_base); exit(0);}
				  else {if (verbose) {printf ("Disk image directory '%s' successfully created\n", dest_dir_base);} }
			break;
							/* if some file with same name exists, abort (should never happen but anyway) */
			case -2 : printf ("Error: '%s' already exists but is not a directory\n", dest_dir_base); exit(0);
		}

		j = range1;
		/* ################### DETERMINE NAME OF .TAP FILE TO SAVE TO AND NAME OF ASSOCIATED LOG FILE #################### */
		dir_index = devildir1_mem[j*19];
		sprintf(name, "%c%c%c%c%c%c%c%c%c%c", \
						devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
						devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
						devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
		for (i=0; i<10; i++) {if (name[i]=='/') {name[i]='_';}}						/* replace '/' with '_' in the .TAP file name being saved to avoid Linux filesystem errors */
		for (i=9; i>=0; i--) {if (name[i]==' ') {name[i]='\0';} else break;}				/* delete trailing spaces from file name */
		strcat(tap_file_name, dest_dir_base); strcat(tap_file_name, "/");
		strcat(tap_file_name, name); strcat(tap_file_name, ".tap");					/* set full path for .TAP file in tap_file_name */
		strcpy(tap_log_file_name, tap_file_name); strcat(tap_log_file_name, ".log");

		/* check if the .TAP file name already exists, and if so try to delete it. */
		/* if the .TAP file name exists and cannot be deleted, terminate with error */
		/* if the .TAP file name exists and is R/O, terminate with error */
		if (access (tap_file_name, F_OK) != -1 ) {
			if (access (tap_file_name, W_OK) != -1) {
				if (unlink(tap_file_name)==0) { printf ("Existing %s file successfully deleted\n", tap_file_name ); }
				else {printf ("Existing %s file could not be deleted. File error %d: %m\n\n", tap_file_name, errno); exit(0);}
			} else {printf ("Existing %s file could not be deleted, file R/O\n", tap_file_name ); exit(0);}
		}
		/* if all OK with the .TAP file name, open the associated log file */
		if (log_on) {
			tap_log_file_stream = fopen (tap_log_file_name, "a");
			if (tap_log_file_stream == NULL) {printf ("readblock() error: log file %s could not be created. file error %d: %m\n\n", tap_log_file_name, errno); exit(0);}
		}

		printf("Saving catalog entries from %d to %d into file %s:\n", range1, range2, tap_file_name);
		if (log_on) {fprintf(tap_log_file_stream, "Saving catalog entries from %d to %d into file %s:\n", range1, range2, tap_file_name);}
		/* ############################################################################### */
		while (j <= range2) {		/* read all data blocks that make up the catalog range specified */
			dir_index = devildir1_mem[j*19];
			printf("-------------------------------\nSaving data block for catalog entry #%d (starting with dir entry %d):\n", j, dir_index);
			if (log_on) {fprintf(tap_log_file_stream, "-------------------------------\nSaving data block for catalog entry #%d (starting with dir entry %d):\n", j, dir_index);}
			switch (devildir0_mem[dir_index*18]) {
				case 0 : printf("> Prog: "); if (log_on) {fprintf(tap_log_file_stream, "> Prog: ");}
				break;
				case 1 : printf("> NArr: "); if (log_on) {fprintf(tap_log_file_stream, "> NArr: ");}
				break;
				case 2 : printf("> CArr: "); if (log_on) {fprintf(tap_log_file_stream, "> CArr: ");}
				break;
				case 3 : printf("> Code: "); if (log_on) {fprintf(tap_log_file_stream, "> Code: ");}
			}
			printf("%c%c%c%c%c%c%c%c%c%c =",devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			if (log_on) {fprintf(tap_log_file_stream, "%c%c%c%c%c%c%c%c%c%c =",devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);}
			leftover_size = devildir0_mem[dir_index*18+11]+devildir0_mem[dir_index*18+12]*256;
			data_length = leftover_size + 2;	/* to be used in the data packing as size (flag byte (FF) plus checksum byte = 2 extra bytes for data block) */
			printf(" %5d bytes\n", leftover_size);
			if (log_on) {fprintf(tap_log_file_stream, " %5d bytes\n", leftover_size);}
			sprintf(name, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			memcpy(&params[0], &devildir0_mem[dir_index*18+11], 6);		/* params = 6 bytes containing length, par1 and par2 */
			i=dir_index; strcpy(name1, name); memcpy(&params1[0], &params[0], 6);
/*			if (debug) {printf ("name = '%s'  name1 = '%s'\n", name, name1);}	*/

			/* ####################### save header data here ######################### */
			/* -------------------- fill out the header buffer ----------------------- */
			memcpy(&header[3], &devildir0_mem[i*18], 17);	/* fill out the data type, name, data length, par1, par2 */
			checksum_h = 0; for (k=2; k<20; k++) {checksum_h ^= header[k];} header[20] = checksum_h;
			header[21] = data_length % 256; header[22] = data_length / 256; header[23] = 0xFF;
			/* -------------------- write out the header buffer ---------------------- */		/* THE .TAP FILE IS CREATED HERE */
			umask(000);			/* ##################### OPEN AND CLOSE .TAP FILE ####################### */
			tap_file_descrptr = open (tap_file_name, O_CREAT | O_WRONLY | O_APPEND, 00666); if (tap_file_descrptr == -1) {printf("devilfilecopy() error: tap_file_descrptr ERROR %d: %m\n", errno); exit(0);}
			error_code = write (tap_file_descrptr, (void *) header, 24); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
			close(tap_file_descrptr);	/* ###################################################################### */
			/* ####################################################################### */

			checksum = 0xFF;	/* initialize read data checksum with the Spectrum data block flag (0xFF) */
			while ((strcmp(name, name1)==0) && ((i==dir_index) || ((devildir0_mem[i*18]==0xE4) && (memcmp(&params[0], &params1[0], 6) == 0)))) {	/* read all tracks for this data block */
				printf ("Saving data from dir entry #%02d (%5d bytes)\n", i, ((leftover_size > 9216) ? 9216 : leftover_size));
				if (log_on) {fprintf(tap_log_file_stream, "Saving data from dir entry #%02d (%5d bytes)\n", i, ((leftover_size > 9216) ? 9216 : leftover_size));}
				/* ####################### save block data here ########################## */
				/* -------------------- read track and save to tap file ------------------ */
				readblock(i, (leftover_size > 9216) ? 9216 : leftover_size);
				/* ####################################################################### */
				i++; if (leftover_size > 9216) {leftover_size -= 9216;}
				sprintf(name1, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[i*18+1], devildir0_mem[i*18+2], devildir0_mem[i*18+3], devildir0_mem[i*18+4], \
							devildir0_mem[i*18+5], devildir0_mem[i*18+6], devildir0_mem[i*18+7], devildir0_mem[i*18+8], \
							devildir0_mem[i*18+9], devildir0_mem[i*18+10]);
				memcpy(&params1[0], &devildir0_mem[i*18+11], 6);
			}
			printf ("-------------------------------\n");
			if (log_on) {fprintf(tap_log_file_stream, "-------------------------------\n");}
			/* ------------------ now write the data checksum byte ---------------------- */
			tap_file_descrptr = open (tap_file_name, O_CREAT | O_WRONLY | O_APPEND, 00666); if (tap_file_descrptr == -1) {printf("readblock() error: tap_file_descrptr ERROR %d: %m\n", errno); exit(0);}
			error_code = write (tap_file_descrptr, (void *) &checksum, 1); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
			close(tap_file_descrptr);
			/* -------------------------------------------------------------------------- */
/*			while (devildir0_mem[i*18] == 0xE5) {i++;}	/* skip over possible deleted tracks (blocks) */
			j++;
		}
		if (log_on) {fclose(tap_log_file_stream);}
	}

	/* Reads a .TAP file and checks all packaged data for errors.
	 * If write == 0 and no errors are encountered returns the number of required tracks on disk.
	 * If write == 0 and errors are encountered, returns -1.
	 * If write != 0 writes all data in the .TAP file to disk, block by block (track by track).
	 * We need to make sure enough continuous free space exists on disk BEFORE writing data from the .TAP file to disk.
	 * That's why this function is meant to be called in 2 different ways: first with write=0 to get number of required tracks,
	 * then with write = 1 and block_no = first block available on disk in a big enough continuous free space. */
	signed char devilfilewrite(char *tap_file_name, unsigned char write, unsigned char block_no) {
		struct stat st;		
		unsigned int tap_file_address;
		unsigned char tracks_needed;		/* total nr of tracks needed for entire .tap file */
		unsigned char tracks_needed1;		/* nr of tracks needed for one .tap data block (Prog, Code etc.) */
		unsigned char header_buffer[256], block_nr;
		unsigned int i;
		char name[11], type[5];
		unsigned short len, len1, count;
		unsigned int size;
		unsigned char err;

		block_nr=block_no;	/* block_nr = current track to write to, will be updated live (block 0 = track 3, tracks 0,1,2 are the directory) */
		
		for (i=0; i<256; i++) {header_buffer[i]=0x00;}		/* bytes 21-255 in a dir entry are always zero and 0-20 will be overwritten with header info */
		stat(tap_file_name, &st); size = st.st_size;
		if (size < 21) {printf("%s: Bad TAP file! File size too small!\n", tap_file_name); return(-1);}
		/* ################ OPEN TAP FILE ################# */
		tap_file_descrptr = open (tap_file_name, O_RDONLY); if (tap_file_descrptr == -1) {printf("readblock() error: tap_file_descrptr ERROR %d: %m\n", errno); return(-1);}
		/* ################################################ */
		tap_file_address = 0;
		tracks_needed = 0;
		err = 0;
		while (tap_file_address < size) {
			/* ################ READ .TAP HEADER DATA ################# */
			count = 21;
			lseek (tap_file_descrptr, tap_file_address, SEEK_SET);
			read (tap_file_descrptr, (void *) tap_file_buffer, count);
			/*---------------------------------------------------- */
			if ((tap_file_buffer[0]!=0x13) || (tap_file_buffer[1]!=0x00) || (tap_file_buffer[2]!=0x00)) {printf("%s: Bad TAP file! first 3 bytes wrong!\n", tap_file_name); err=1; break;}
			for (i=4; i<=13; i++) {if (tap_file_buffer[i] > 127) {printf("%s: Bad TAP file! non-ASCII characters in name!\n", tap_file_name); err=1; break;}}
			memcpy(&name[0], &tap_file_buffer[4], 10); name[10]='\0';
			len = tap_file_buffer[14] + tap_file_buffer[15]*256; type[4] = '\0';
			switch (tap_file_buffer[3]) {
				case 0 : strcpy(type, "Prog"); header_buffer[17]=0x00; break;
				case 1 : strcpy(type, "NArr"); header_buffer[17]=0x00; break;
				case 2 : strcpy(type, "CArr"); header_buffer[17]=0x00; break;
				case 3 : strcpy(type, "Code"); header_buffer[17]=0x01;
			}
			if (write == 0) {
				if (tap_file_address == 0) {printf("\n----------------------------------------------------------\n                   .TAP FILE CONTENTS:\n----------------------------------------------------------\n");}
				printf("%s:  %s  size = %5d  --> ", type, name, len);
			}
			else {
				if (verbose) {printf("------------------------------------------------\n");}
				printf("Writing %s:  %s  size = %5d to disk from track %d:\n", type, name, len, block_nr+3);
				if (verbose) {printf("------------------------------------------------\n");}
				
			}
			checksum=0; for (i=2; i<=19; i++) {checksum ^= tap_file_buffer[i];}
			if (checksum != tap_file_buffer[20]) {printf("Header error: bad checksum!\n"); err=1; break;}
			if (size < 25+len) {printf("Bad size: TAP file too small!\n"); err=1; break;}
			if (write) {	/* ################# WRITE THE FIRST DIR ENTRY FOR THIS .TAP DATA BLOCK #################### */
				memcpy(&header_buffer[0], &tap_file_buffer[3], 17);
				diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("readblock() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}
				if (verbose) {printf("writing dir entry #1 to track %2d sector %2d\n", block_nr/36, xlt2[block_nr%36]+1);}
				error_code = pwrite (diskimg_file_descrptr, (void *) header_buffer, 256, track_length*(block_nr/36)+xlt2[block_nr%36]*256); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
				fsync (diskimg_file_descrptr);
			}
			/* ################ READ .TAP BLOCK DATA ################## */
			tap_file_address += 21; count = len+4;
			lseek (tap_file_descrptr, tap_file_address, SEEK_SET);
			read (tap_file_descrptr, (void *) tap_file_buffer, count);
			/*---------------------------------------------------- */
			checksum = tap_file_buffer[2];
			for (i=3; i<count-1; i++) {checksum ^= tap_file_buffer[i];}
			len1 = tap_file_buffer[0] + tap_file_buffer[1]*256;
			if (len1 != len+2) {printf("Data block error: data length does not match!\n"); err=1; break;}
			tracks_needed += (len%9216) ? len/9216+1 : len/9216;
			tracks_needed1 = (len%9216) ? len/9216+1 : len/9216;
			if (checksum != tap_file_buffer[count-1]) {printf("Data block error: bad checksum!\n"); err=1; break;}
				else { if (write == 0) {printf("OK, tracks needed: %d\n", tracks_needed1);} }
			tap_file_address += count;
			if (write) {
				if (tracks_needed1 > 1) {	/* ################# WRITE THE REST OF THE DIR ENTRIES FOR THIS .TAP DATA BLOCK ################### */
					header_buffer[0]=0XE4;		/* flag for extra dir entries of the same data block */
					for (i=1; i<tracks_needed1; i++) {
						if (verbose) {printf("writing dir entry #%d to track %2d sector %2d\n", i+1, (block_nr+i)/36, xlt2[(block_nr+i)%36]+1);}
						error_code = pwrite (diskimg_file_descrptr, (void *) header_buffer, 256, track_length*((block_nr+i)/36)+xlt2[(block_nr+i)%36]*256);
						if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
					}
				}
				for (i=0; i<len/9216; i++) {	/* ################# WRITE ALL FULL TRACKS FOR THIS .TAP DATA BLOCK ################### */
					if (verbose) {printf("Writing     block #%d to track %2d\n", i+1, block_nr+3+i);}
					memcpy(&track_buffer[0], &tap_file_buffer[3+i*9216], 9216);
					writeblock(block_nr+i, 9216);
				}				/* at this point i = len/9216 */
				if (len%9216) {			/* ################# WRITE LAST (PARTIAL) TRACK (IF ANY) FOR THIS .TAP DATA BLOCK ################### */
					if (verbose) {printf("Writing     block #%d to track %2d\n", i+1, block_nr+3+i);}
					memcpy(&track_buffer[0], &tap_file_buffer[3+i*9216], len%9216);
					writeblock(block_nr+i, len%9216);
				}
			}
			block_nr += tracks_needed1;
			if (write && verbose) {printf("------------------------------------------------\n");}
		}
		close (tap_file_descrptr);
		if (err == 1) {return(-1);} else {return(tracks_needed);}
	}

	void devilfiledelete(unsigned char range1, unsigned char range2) {

		unsigned char write_buffer[256];	/* save buffer holding 0xE5 bytes */
		for (i=0; i<256; i++) {write_buffer[i]=0xE5;}
		unsigned short leftover_size /*, data_length */;
		char name[11], name1[11];		/* standard 10 char Spectrum program name */
		unsigned char params[6], params1[6];
		unsigned char dir_index;
		int i, j, k;

		diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("devilfiledelete() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}

		j = range1;
		printf("Deleting catalog entries from %d to %d:\n", range1, range2);
		/* ############################################################################### */
		while (j <= range2) {		/* read all data blocks that make up the catalog range specified */
			dir_index = devildir1_mem[j*19];
			printf ("-------------------------------\n");
			printf("Deleting data block for catalog entry #%d (starting with dir entry %d):\n", j, dir_index);
			switch (devildir0_mem[dir_index*18]) {
				case 0 : printf("> Prog: ");
				break;
				case 1 : printf("> NArr: ");
				break;
				case 2 : printf("> CArr: ");
				break;
				case 3 : printf("> Code: ");
			}
			printf("%c%c%c%c%c%c%c%c%c%c =",devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			leftover_size = devildir0_mem[dir_index*18+11]+devildir0_mem[dir_index*18+12]*256;
/*			data_length = leftover_size + 2;	/* to be used in the data packing as size (flag byte (FF) plus checksum byte = 2 extra bytes for data block) */
			printf(" %5d bytes\n", leftover_size);
			sprintf(name, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			memcpy(&params[0], &devildir0_mem[dir_index*18+11], 6);		/* params = 6 bytes containing length, par1 and par2 */
			i=dir_index; strcpy(name1, name); memcpy(&params1[0], &params[0], 6);
/*			if (debug) {printf ("name = '%s'  name1 = '%s'\n", name, name1);}	*/

			/* ######### delete each dir entry first and then its data (by filling with E5) ######### */

			while ((strcmp(name, name1)==0) && ((i==dir_index) || ((devildir0_mem[i*18]==0xE4) && (memcmp(&params[0], &params1[0], 6) == 0)))) {	/* delete all tracks for this data block */
				/* ####################### delete dir entry here ########################### */
				printf ("Deleting dir entry #%02d\n", i);
				error_code = pwrite (diskimg_file_descrptr, (void *) write_buffer, 256, track_length*(i/36)+xlt2[i%36]*256); if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
				fsync (diskimg_file_descrptr);
				/* ####################### delete block data here ########################## */
				printf ("Deleting data for dir entry #%02d (%5d bytes)\n", i, ((leftover_size > 9216) ? 9216 : leftover_size));
				for (k=0; k<36; k++) {
					error_code = pwrite (diskimg_file_descrptr, (void *) write_buffer, 256, track_length*(3+i)+k*256);
					if (error_code == -1) {printf("WRITE ERROR %d: %m\n", errno);}
				}
				/* ####################################################################### */
				i++; if (leftover_size > 9216) {leftover_size -= 9216;}
				sprintf(name1, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[i*18+1], devildir0_mem[i*18+2], devildir0_mem[i*18+3], devildir0_mem[i*18+4], \
							devildir0_mem[i*18+5], devildir0_mem[i*18+6], devildir0_mem[i*18+7], devildir0_mem[i*18+8], \
							devildir0_mem[i*18+9], devildir0_mem[i*18+10]);
				memcpy(&params1[0], &devildir0_mem[i*18+11], 6);
			}
			printf ("-------------------------------\n");
			j++;
		}

		close(diskimg_file_descrptr);
	/*	diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("devilfiledelete() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);} */

	}

	void devilfilesetattr(unsigned char range1, unsigned char range2, char* sys) {

		unsigned char write_buffer[1];		/* save buffer holding sys byte */
		unsigned short leftover_size		/*, data_length */;
		char name[11], name1[11];		/* standard 10 char Spectrum program name */
		unsigned char params[6], params1[6];
		unsigned char dir_index, attr_no;
		char attr[4];
		if (strcmp(sys,"-r0")==0) {strcpy(attr,"R/W"); attr_no = 0;}
		if (strcmp(sys,"-r1")==0) {strcpy(attr,"R/O"); attr_no = 1;}
		if (strcmp(sys,"-s0")==0) {strcpy(attr,"DIR"); attr_no = 2;}
		if (strcmp(sys,"-s1")==0) {strcpy(attr,"SYS"); attr_no = 3;}
		int i, j;

		diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("devilfilesetattr() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}

		j = range1;
		printf("Setting %s attribute for catalog entries from %d to %d:\n", attr, range1, range2);
		/* ############################################################################### */
		while (j <= range2) {		/* read all data blocks that make up the catalog range specified */
			dir_index = devildir1_mem[j*19];
			if (verbose) {printf("---------------------------------------\n");}
			if (verbose) {printf("Setting %s attribute for catalog entry #%d (starting with dir entry %d):\n", attr, j, dir_index);}
			switch (devildir0_mem[dir_index*18]) {
				case 0 : printf("> Prog: ");
				break;
				case 1 : printf("> NArr: ");
				break;
				case 2 : printf("> CArr: ");
				break;
				case 3 : printf("> Code: ");
			}
			printf("%c%c%c%c%c%c%c%c%c%c =",devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			leftover_size = devildir0_mem[dir_index*18+11]+devildir0_mem[dir_index*18+12]*256;
/*			data_length = leftover_size + 2;	/* to be used in the data packing as size (flag byte (FF) plus checksum byte = 2 extra bytes for data block) */
			printf(" %5d bytes\n", leftover_size);
			sprintf(name, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[dir_index*18+1], devildir0_mem[dir_index*18+2], devildir0_mem[dir_index*18+3], devildir0_mem[dir_index*18+4], \
							devildir0_mem[dir_index*18+5], devildir0_mem[dir_index*18+6], devildir0_mem[dir_index*18+7], devildir0_mem[dir_index*18+8], \
							devildir0_mem[dir_index*18+9], devildir0_mem[dir_index*18+10]);
			memcpy(&params[0], &devildir0_mem[dir_index*18+11], 6);		/* params = 6 bytes containing length, par1 and par2 */
			i=dir_index; strcpy(name1, name); memcpy(&params1[0], &params[0], 6);
/*			if (debug) {printf ("name = '%s'  name1 = '%s'\n", name, name1);}	*/

			/* ######### set attr for each dir entry ######### */

			while ((strcmp(name, name1)==0) && ((i==dir_index) || ((devildir0_mem[i*18]==0xE4) && (memcmp(&params[0], &params1[0], 6) == 0)))) {	/* set attr for all entries of this data block */
				write_buffer[0] = devildir0_mem[i*18+17];			/* get attribute byte from devildir0_mem */
				switch (attr_no) {
					case 0 : write_buffer[0] &= ~(1 << 1); break;		/* reset bit 1 for R/W */
					case 1 : write_buffer[0] |=  (1 << 1); break;		/*   set bit 1 for R/O */
					case 2 : write_buffer[0] &= ~(1 << 0); break;		/* reset bit 0 for DIR */
					case 3 : write_buffer[0] |=  (1 << 0); break;		/*   set bit 0 for SYS */
				}
				/* ####################### set attr of dir entry here ########################### */
				if (verbose) {printf ("Setting %s attribute for dir entry #%02d\n", attr, i);}
/*				printf ("sector start = %d\n", track_length*(i/36)+xlt2[i%36]*256+17);		/* testing */
				error_code = pwrite (diskimg_file_descrptr, (void *) write_buffer, 1, track_length*(i/36)+xlt2[i%36]*256+17); if (error_code == -1) {printf("devilfilesetattr() error: WRITE ERROR %d: %m\n", errno);}
				fsync (diskimg_file_descrptr);
				/* ####################################################################### */
				i++; if (leftover_size > 9216) {leftover_size -= 9216;}
				sprintf(name1, "%c%c%c%c%c%c%c%c%c%c", \
							devildir0_mem[i*18+1], devildir0_mem[i*18+2], devildir0_mem[i*18+3], devildir0_mem[i*18+4], \
							devildir0_mem[i*18+5], devildir0_mem[i*18+6], devildir0_mem[i*18+7], devildir0_mem[i*18+8], \
							devildir0_mem[i*18+9], devildir0_mem[i*18+10]);
				memcpy(&params1[0], &devildir0_mem[i*18+11], 6);
			}
			if (verbose) {printf ("---------------------------------------\n");}
			j++;
		}

		close(diskimg_file_descrptr);
	/*	diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("devilfilesetattr() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);} */

	}

	void interleave() {
		int diskimg_intl_file_descrptr, i, sect_start;
		unsigned char write_buffer[256];
		char diskimg_intl_file_name[100];

		for (i=0; i<100; i++) {diskimg_intl_file_name[i]='\0';}
/*		printf("diskimg_file_name = %s\n", diskimg_file_name);							/* testing */
		strcat(diskimg_intl_file_name, diskimg_file_name);
		if (strstr(diskimg_intl_file_name,".img")) {
			diskimg_intl_file_name[strstr(diskimg_intl_file_name,".img")-&diskimg_intl_file_name[0]]='\0'; strcat(diskimg_intl_file_name, "_interleaved.img");
		} else {
			printf("'%s': Disk image file name must have the extension .img.\n", basename(diskimg_file_name)); exit(0);
		}

		printf("\nInterleaving sectors for image %s:\n", diskimg_file_name);
		diskimg_file_descrptr = open (diskimg_file_name, O_RDONLY); if (diskimg_file_descrptr == -1) {printf("interleave() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}

		if (verbose) {printf("Attempting to create file for interleaved disk image %s...\n", diskimg_intl_file_name);}
		if (xis_dir(diskimg_intl_file_name)!=-1) {printf("Error: file '%s' already exists. File creation aborted.\n", diskimg_intl_file_name); exit(0);}
		umask(000);
		diskimg_intl_file_descrptr = open (diskimg_intl_file_name, O_RDWR | O_CREAT, 00666);
		if (diskimg_intl_file_descrptr == -1) {printf("File creation error %d: %m", errno); exit(0);}
		for (i=0; i<72*1024; i++) {write (diskimg_intl_file_descrptr, (void *) buffer, 10);}	/* writing 10 bytes at once */
		if (verbose) {printf("Disk image file %s successfully created\n", diskimg_intl_file_name);}

		printf("\n\n-----------------------------------------------------------\nCopying sectors from %s to %s with interleave 2 ...\n", diskimg_file_name, diskimg_intl_file_name);
		printf(" Copying sector: 0000");
		for (i=0; i<2880; i++) { /* 80 tracks x 2 sides x 18 sectors = 2880 total sectors on a disk*/
			printf("\b\b\b\b%4d", i+1);
			error_code = pread (diskimg_file_descrptr, (void *) write_buffer, 256, i*256); if (error_code == -1) {printf("\ninterleave() error: WRITE ERROR %d: %m\n", errno); break;}
			sect_start = (i/18)*sstracklength + xlt[i%18]*256;
			error_code = pwrite (diskimg_intl_file_descrptr, (void *) write_buffer, 256, sect_start); if (error_code == -1) {printf("\ninterleave() error: WRITE ERROR %d: %m\n", errno); break;}
		}
		printf("\n-----------------------------------------------------------\n\n");
		printf("Interleaved image saved as %s\n\n", diskimg_intl_file_name);

		close(diskimg_file_descrptr);
		close(diskimg_intl_file_descrptr);

	}

	void deinterleave() {
		int diskimg_deintl_file_descrptr, i, sect_start;
		unsigned char write_buffer[256];
		char diskimg_deintl_file_name[100];

		for (i=0; i<100; i++) {diskimg_deintl_file_name[i]='\0';}
		strcat(diskimg_deintl_file_name, diskimg_file_name);
		if (strstr(diskimg_deintl_file_name,".img")) {
			diskimg_deintl_file_name[strstr(diskimg_deintl_file_name,".img")-&diskimg_deintl_file_name[0]]='\0'; strcat(diskimg_deintl_file_name, "_deinterleaved.img");
		} else {
			printf("'%s': Disk image file name must have the extension .img.\n", basename(diskimg_file_name)); exit(0);
		}

		printf("\nDeinterleaving sectors for image %s:\n", diskimg_file_name);
		diskimg_file_descrptr = open (diskimg_file_name, O_RDONLY); if (diskimg_file_descrptr == -1) {printf("interleave() error: diskimg_file_descrptr ERROR %d: %m\n", errno); exit(0);}

		if (verbose) {printf("Attempting to create file for deinterleaved disk image %s...\n", diskimg_deintl_file_name);}
		if (xis_dir(diskimg_deintl_file_name)!=-1) {printf("Error: file '%s' already exists. File creation aborted.\n", diskimg_deintl_file_name); exit(0);}
		umask(000);
		diskimg_deintl_file_descrptr = open (diskimg_deintl_file_name, O_RDWR | O_CREAT, 00666);
		if (diskimg_deintl_file_descrptr == -1) {printf("File creation error %d: %m", errno); exit(0);}
		for (i=0; i<72*1024; i++) {write (diskimg_deintl_file_descrptr, (void *) buffer, 10);}	/* writing 10 bytes at once */
		if (verbose) {printf("Disk image file %s successfully created\n", diskimg_deintl_file_name);}

		printf("\n\n-----------------------------------------------------------\nCopying sectors from %s to %s without interleave ...\n", diskimg_file_name, diskimg_deintl_file_name);
		printf(" Copying sector: 0001");
		for (i=0; i<2880; i++) { /* 80 tracks x 2 sides x 18 sectors = 2880 total sectors on a disk*/
			printf("\b\b\b\b%4d", i+1);
			sect_start = (i/18)*sstracklength + xlt[i%18]*256;
			error_code = pread (diskimg_file_descrptr, (void *) write_buffer, 256, sect_start); if (error_code == -1) {printf("\ndeinterleave() error: WRITE ERROR %d: %m\n", errno); break;}
			error_code = pwrite (diskimg_deintl_file_descrptr, (void *) write_buffer, 256, i*256); if (error_code == -1) {printf("\ndeinterleave() error: WRITE ERROR %d: %m\n", errno); break;}
		}
		printf("\n-----------------------------------------------------------\n\n");
		printf("Deinterleaved image saved as %s\n\n", diskimg_deintl_file_name);

		close(diskimg_file_descrptr);
		close(diskimg_deintl_file_descrptr);

	}
	/* ##################################################### PROCEDURE UNDER CONSTRUCTION ################################################################ */
	void diskcheck () {
		unsigned int dir_sect_start;
		unsigned short i, j;	/*, k, crt_block_number, crt_block_value; */
/*		unsigned char alloc_blocks[77];		/* map of free and allocated blocks */
		unsigned char rec_check_map[77];
		unsigned char rb[17], rb1[17];		/* comparison buffer */
		unsigned char rb2[256];
		unsigned char warn=0;	/*, warn1=0; */
		int diskimg_file_descrptr;		/* disk image file descriptor */

		/* ################ open disk image file for writing ################### */
		diskimg_file_descrptr = open (diskimg_file_name, O_RDWR); if (diskimg_file_descrptr == -1) {printf("diskimg_file_descrptr ERROR %d: %m\n", errno);}

		/* ################ CHECKING FOR DUPLICATE DIRECTORY RECORDS ################# */
		warn=0;
		for (i = 0; i < 77; i++) {rec_check_map[i]=0;}
		printf("Checking for duplicate directory records ... ");
		for (i = 0; (i < nr_recs) && (rec_check_map[i]==0); i++) {
			memcpy(&rb[0], (&devildir_mem[0] + i*19 + 1), 17);
			for (j=i+1; (j<nr_recs) && (rec_check_map[j]==0); j++) {
				memcpy(&rb1[0], (&devildir_mem[0] + j*19 + 1), 17);
				if (memcmp(&rb[0], &rb1[0], 17) == 0) {rec_check_map[i]=1; rec_check_map[j]=1; printf("\nFound duplicate dir records #%d and #%d for file '%c%c%c%c%c%c%c%c%c%c'", i, j, devildir_mem[0+i*19+2], devildir_mem[0+i*19+3], devildir_mem[0+i*19+4], devildir_mem[0+i*19+5], devildir_mem[0+i*19+6], devildir_mem[0+i*19+7], devildir_mem[0+i*19+8], devildir_mem[0+i*19+9], devildir_mem[0+i*19+10], devildir_mem[0+i*19+11]); warn=1;}
			}
		}
		if (warn == 0) {printf("none found - OK!\n");} else {printf("\n");}

		/* ################ CHECKING FOR WRONG BYTES IN DIRECTORY RECORDS ################# */
		warn=0;
		printf("Checking for wrong bytes in directory records ... ");
		for (i = 0; i < 77; i++) {
			dir_sect_start = (i/18)*sstracklength + xlt[i%18]*256;
			lseek (diskimg_file_descrptr, dir_sect_start, SEEK_SET);
			read (diskimg_file_descrptr, (void *) rb2, 256);
			if ((rb2[0]>3) && (rb2[0]!=0xE4)) {printf("\nWrong flag byte in dir record #%d: %2X", i, rb2[0]); warn=1;}
			if ((rb2[0]<=3)) {for (j=18; j<256; j++) {if (rb2[j]!=0) {printf("\nNon-zero byte #%d in unused dir record #%d area.", j, i); warn=1; break;}}}
		}
		if (warn == 0) {printf("none found - OK!\n");} else {printf("\n");}

		/* ################ close disk image file ################### */
		close(diskimg_file_descrptr);
	}

	/* ########################################################################################################################################################## */

/*	############################## .TAP FILE FORMAT ###################################

	#----------------------------- HEADER STRUCTURE ----------------------------------#

	  00   01   02   03   04   05   06   07   08   09   10   11   12   13   14   15   16   17   18   19   20
	+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
	| L0 | L1 | FL | TY | N0 | N1 | N2 | N3 | N4 | N5 | N6 | N7 | N8 | N9 | D0 | D1 | A0 | A1 | B0 | B1 | CK |
	+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

	Field meanings:		L0 = HEADER DATA LENGTH FROM FLAG TO CHECKSUM (Low byte)	FL = FLAG BYTE (00 for header)
				L1 = HEADER DATA LENGTH FROM FLAG TO CHECKSUM (High byte)	TY = DATA TYPE (00 = program, 01 = number array, 02 = character array, 03 = code)
				
				N0...N9 = DATA BLOCK NAME (10 characters, padded with trailing blanks)

				D0 = DATA BLOCK LENGTH (Low byte)	A0 = PARAMETER 1 (Low byte)	B0 = PARAMETER 2 (Low byte)
				D1 = DATA BLOCK LENGTH (High byte)	A1 = PARAMETER 1 (High byte)	B1 = PARAMETER 2 (High byte)

				CK = XOR CHECKSUM FOR BYTES 02...19

	#----------------------------- DATA BLOCK STRUCTURE ------------------------------#

	  00   01   02   03   04   05            nn-2 nn-1  nn
	+----+----+----+----+----+----+         +----+----+----+
	| L0 | L1 | FL | XX | XX | XX |  . . .  | XX | XX | CK |
	+----+----+----+----+----+----+         +----+----+----+

	Field meanings:		L0 = DATA BLOCK LENGTH FROM FLAG TO CHECKSUM (02...nn) (Low byte)	FL = FLAG BYTE (FF for data block)
				L1 = DATA BLOCK LENGTH FROM FLAG TO CHECKSUM (02...nn) (High byte)	XX = DATA BYTES

				CK = XOR CHECKSUM FOR BYTES 02...nn-1

*/
	/* ############################################################################################################################### */
	/* ############################################# BEGINNING OF ACTUAL main() CODE ################################################# */
	/* ############################################################################################################################### */

	long size = 0;					/* size of filename argument provided */

	if (argc <= 2) {printf (USAGE); exit (0);}	/* print usage instructions if not enough args */

	/* ########################## command line param #1 check - command to execute ############################## */
	if ( !((strcmp(argv[1],"-c") == 0) || (strcmp(argv[1],"-x") == 0) || (strcmp(argv[1],"-l") == 0) || (strcmp(argv[1],"-w") == 0) || (strcmp(argv[1],"-d") == 0) || (strcmp(argv[1],"-k") == 0) || (strcmp(argv[1],"-i") == 0) || (strcmp(argv[1],"-di") == 0) || (strcmp(argv[1],"-r0") == 0) || (strcmp(argv[1],"-r1") == 0) || (strcmp(argv[1],"-s0") == 0) || (strcmp(argv[1],"-s1") == 0)) ) {printf ("Invalid command: %s\n", argv[1]); exit(0);}

	if (strcmp(argv[1],"-c") == 0) {create_mode=1;}
	if (strcmp(argv[1],"-l") == 0) {list_mode=1;}
	if (strcmp(argv[1],"-x") == 0) {extract_mode=1;}
	if (strcmp(argv[1],"-w") == 0) {write_mode=1;}
	if (strcmp(argv[1],"-d") == 0) {delete_mode=1;}
	if (strcmp(argv[1],"-k") == 0) {check_mode=1;}
	if (strcmp(argv[1],"-i") == 0) {interleave_mode=1;}
	if (strcmp(argv[1],"-di") == 0) {deinterleave_mode=1;}
	if ((strcmp(argv[1],"-r0") == 0) || (strcmp(argv[1],"-r1") == 0) || (strcmp(argv[1],"-s0") == 0) || (strcmp(argv[1],"-s1") == 0)) {set_attr_mode=1;}

	/* ######################### create empty formatted disk image:   executable -c diskimg_file_name ######################### */
	if (create_mode) {
		if (argc > 3) {printf ("Too many parameters after the -c command\n"); exit(0);}
		printf("Attempting to create disk image %s...\n", argv[2]);
		if (xis_dir(argv[2])!=-1) {printf("Error: file '%s' already exists. Disk image creation aborted.\n", argv[2]); exit(0);}
		umask(000);
		diskimg_file_name = argv[2];		/* initialize diskimg_file_name */
		diskimg_file_descrptr = open (diskimg_file_name, O_RDWR | O_CREAT, 00666);
		if (diskimg_file_descrptr == -1) {printf("File creation error %d: %m", errno); exit(0);}
		for (i=0; i<72*1024; i++) {write (diskimg_file_descrptr, (void *) buffer, 10);}	/* writing 10 bytes at once */
		close(diskimg_file_descrptr);
		printf("Disk image %s successfully created\n", diskimg_file_name);
		exit(0);
	}

	/* ########################## command line param #2 check - disk image file name ############################## */
	if (list_mode || extract_mode || (write_mode && (xis_dir(argv[2])!=1)) || delete_mode || interleave_mode || deinterleave_mode || set_attr_mode || check_mode) {
		diskimg_file_name = argv[2];		/* initialize diskimg_file_name */
		if (dotcount(basename(diskimg_file_name)) > 1) {printf("'%s': No multiple dots allowed in disk image file name.\n", basename(diskimg_file_name)); exit(0);}
		if (dotcount(basename(diskimg_file_name)) == 0) {printf("'%s': Disk image file name must have the extension .img.\n", basename(diskimg_file_name)); exit(0);}
		if ((dotcount(basename(diskimg_file_name)) == 1) && !(strstr(basename(diskimg_file_name), ".img") && (dotpos(basename(diskimg_file_name)) == strlen(basename(diskimg_file_name))-4))) {printf("'%s': Disk image file name must have the extension .img.\n", basename(diskimg_file_name)); exit(0);}

		test_stream = fopen (diskimg_file_name, "r"); if (test_stream == NULL) {printf ("Disk image file error %d: %m\n", errno); exit(0);}
		stat(diskimg_file_name, &st); size = st.st_size;
		fclose(test_stream);
		if (size != 737280) {printf ("Invalid Devil disk image file specified - file size = %ld\n", size); exit(0);}
	}

	/* ############ list command syntax check:   executable -l diskimg_file_name [-deb]   ############# */
	if (list_mode) {	/* cmd line syntax check for list command */
		if (argc == 4) {
			if (strcmp(argv[3], "-deb") != 0) {printf ("Invalid option: %s\n", argv[3]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 4) {printf ("Too many parameters after the -l command\n"); exit(0);}
	}

	/* ############ extract file command syntax check:   executable -x diskimg_file_name range [-log|-ver [-deb]]   ############# */
	if (extract_mode) {	/* cmd line syntax check for extract file command */
		if (argc == 3) {printf ("Please specify a file or range of files you want to extract (ex. 5 or 21-34)\n"); exit(0);}
		if (argc >= 4) {	/* check range syntax */
			range_check();
			printf("\nExtract command syntax check: Catalog entries to process: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);
		}
		if (argc >= 5) {	/* check -log|-ver syntax */
			if ((strcmp (argv[4], "-log") != 0) && (strcmp (argv[4], "-ver") != 0)) {printf("Invalid option: %s\n", argv[4]); exit(0);}
			if (strcmp (argv[4], "-log") == 0) {printf ("Operation log will be generated for each file\n"); log_on = 1;}
			if (strcmp (argv[4], "-ver") == 0) {printf ("Verbose operation.\n"); verbose = 1;}
		}
		if (argc == 6) {	/* check -deb syntax */
			if (strcmp (argv[5], "-deb") != 0) {printf ("Invalid option: %s\n", argv[5]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 6) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	/* ############ write command syntax check: executable -w diskimg_file_name path/to/tap_file [-ver [-deb]] ############# */
/*	if (write_mode && (xis_dir(argv[2])==1)) {	*/
	if (write_mode && (access ( argv[3], F_OK ) == 0)) {
		if (argc >= 5) {
			if (strcmp (argv[4], "-ver") != 0) {printf ("Invalid option: %s\n", argv[4]); exit(0);}
			if (strcmp (argv[4], "-ver") == 0) {printf ("Verbose operation.\n"); verbose = 1;}
		}
		if (argc == 6) {
			if (strcmp (argv[5], "-deb") != 0) {printf ("Invalid option: %s\n", argv[5]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		
		if (argc > 6) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	/* ############ delete file command syntax check:   executable -d diskimg_file_name range [-ver [-deb]]   ############# */
	if (delete_mode) {	/* cmd line syntax check for delete file command */
		if (argc == 3) {printf ("Please specify a file or range of files you want to delete (ex. 5 or 21-34)\n"); exit(0);}
		if (argc >= 4) {	/* check range syntax */
			range_check();
			printf("\nDelete command syntax check: Catalog entries to process: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);
		}
		if (argc >= 5) {	/* check -log|-ver syntax */
			if (strcmp (argv[4], "-ver") != 0) {printf("Invalid option: %s\n", argv[4]); exit(0);}
			else {printf ("Verbose operation.\n"); verbose = 1;}
		}
		if (argc == 6) {	/* check -deb syntax */
			if (strcmp (argv[5], "-deb") != 0) {printf ("Invalid option: %s\n", argv[5]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 6) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	/* ############ set attr command syntax check:   executable -s0|-s1 diskimg_file_name range [-ver [-deb]]   ############# */
	if (set_attr_mode) {	/* cmd line syntax check for set attr command */
		if (argc == 3) {printf ("Please specify a file or range of files you want to delete (ex. 5 or 21-34)\n"); exit(0);}
		if (argc >= 4) {	/* check range syntax */
			range_check();
			printf("Set attr command syntax check: Catalog entries to process: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);
		}
		if (argc >= 5) {	/* check -ver syntax */
			if (strcmp (argv[4], "-ver") != 0) {printf("Invalid option: %s\n", argv[4]); exit(0);}
			else {printf ("Verbose operation.\n"); verbose = 1;}
		}
		if (argc == 6) {	/* check -deb syntax */
			if (strcmp (argv[5], "-deb") != 0) {printf ("Invalid option: %s\n", argv[5]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 6) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	/* ############ interleave/deinterleave command syntax check:   executable -i diskimg_file_name [-ver [-deb]]   ############# */
	if (interleave_mode || deinterleave_mode) {	/* cmd line syntax check for interleave/deinterleave command */
		if (argc >= 4) {	/* check -ver syntax */
			if (strcmp (argv[4], "-ver") != 0) {printf("Invalid option: %s\n", argv[4]); exit(0);}
			else {printf ("Verbose operation.\n"); verbose = 1;}
		}
		if (argc == 5) {	/* check -deb syntax */
			if (strcmp (argv[5], "-deb") != 0) {printf ("Invalid option: %s\n", argv[5]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 5) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	/* ############ error check command syntax check:   executable -k diskimg_file_name range [-deb]   ############# */
	if (check_mode) {	/* cmd line syntax check for extract file command */
		if (argc == 4) {	/* check -log|-ver syntax */
			if (strcmp (argv[3], "-deb") != 0) {printf("Invalid option: %s\n", argv[4]); exit(0);}
			else {printf ("Debug messages on.\n"); debug = 1;}
		}
		if (argc > 4) {
			printf("Too many arguments, exiting.\n"); exit(0);
		}
	}

	devildirdump();
	if (extract_mode && debug) {printf("Catalog entries to extract: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);}
	if (delete_mode && debug) {printf("Catalog entries to delete: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);}
	if (set_attr_mode && debug) {printf("Catalog entries to set attr: %s to %s (%d to %d)\n", range1_c, range2_c, range1, range2);}

	/* ##################################################################################################################### */
	/* ########################################### EXTRACTION MODE COMMANDS ################################################ */

	if (extract_mode && (argc >= 4)) {	/* if minimum arguments required exist */
		devilfilecopy(range1, range2);
	}

	/* ##################################################################################################################### */
	/* ########################################### WRITE MODE COMMANDS ##################################################### */

	if (write_mode && (argc >= 3)) {	/* if minimum arguments required exist */
		blocks_needed = devilfilewrite(argv[3], 0, 0);
		if (blocks_needed == -1) {printf("Errors encountered, exiting.\n"); exit(0);} else {
			printf("----------------------------------------------------------\nTotal tracks required: %d\n\n", blocks_needed);
			i=0; j=0;
			while (i<77) {
				while ((i<77) && (alloc_dir[i]==1)) {i++;}
				if (i==76) {printf("Disk image full, writing aborted.\n"); exit(0);}
				else {
					k=i;
					while ((i<77) && (alloc_dir[i]==0) && (j<blocks_needed)) {i++; j++;}
					if (j == blocks_needed) {printf("Found enough continuous free space at block %d (track %d)\n\n", k, k+3); devilfilewrite(argv[3], 1, k); exit(0);} else {j=0;}
				}
			}
			printf ("Not enough continuous free space available on disk, writing aborted.\n"); exit(0);
		}
	}

	/* ################################################################################################################ */
	/* ########################################### DELETION MODE COMMANDS ############################################# */

	if (delete_mode && (argc >= 4)) {	/* if minimum arguments required exist */
		devilfiledelete(range1, range2);
	}

	/* ################################################################################################################ */
	/* ########################################### INTERLEAVE MODE COMMANDS ########################################### */
	
	if (interleave_mode && (argc >= 3)) {
		interleave();
	}

	/* ################################################################################################################ */
	/* ########################################### DEINTERLEAVE MODE COMMANDS ######################################### */
	
	if (deinterleave_mode && (argc >= 3)) {
		deinterleave();
	}

	/* ################################################################################################################ */
	/* ########################################### SET ATTR MODE COMMANDS ############################################# */

	if (set_attr_mode && (argc >= 4)) {	/* if minimum arguments required exist */
		devilfilesetattr(range1, range2, argv[1]);
	}

	/* ################################################################################################################ */
	/* ########################################### CHECK MODE COMMANDS ################################################ */

	if (check_mode && (argc >= 3)) {	/* if correct nr of arguments required */
		diskcheck();
	}

	return(0);
}